/* Nate Deisinger (ndeisinger@wisc.edu)
 *
 * This file is part of soot-csi, a project to apply lightweight
 * instrumentation to Java programs to facilitate debugging through
 * the Soot analysis framework.
 *
 * Copyright (c) 2014, Nate Deisinger, University of Wisconsin-Madison
 *   Madison, Wisconsin 53715
 *   All Rights Reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc.,
 *   51 Franklin Street, Fifth Floor,
 *   Boston, MA  02110-1301
 *   USA
 * or retrieve version 2.1 at their website:
 *   http://www.gnu.org/licenses/lgpl-2.1.html
 */
package uwmadison.soot.analysis;

import java.util.LinkedList;

import soot.Body;
import soot.BooleanType;
import soot.Modifier;
import soot.Scene;
import soot.SootClass;
import soot.SootField;
import soot.SootMethod;
import soot.Type;
import soot.Unit;
import soot.*;
import soot.jimple.AssignStmt;
import soot.jimple.InvokeStmt;
import soot.jimple.Jimple;
import soot.jimple.Stmt;
import soot.jimple.StringConstant;
import soot.jimple.VirtualInvokeExpr;

/**
 * Utility class for our CallAnalysis class.
 * @author Nate Deisinger
 */
public class CallUtility {

    private static SootClass callClass, systemClass, printClass, stringClass;
    private static SootMethod dumpMethod, 
    printStringMethod, printBoolMethod, printLineMethod;
    private static SootField systemErr;
    private static Local printWriter, printBool = null;

    /**
     * Performs initialization work to link Soot objects to parts of code.
     */
    static
    {
        Scene.v().loadClassAndSupport("uwmadison.soot.analysis.CallTrace");
        callClass = Scene.v().getSootClass("uwmadison.soot.analysis.CallTrace");
        systemClass = Scene.v().getSootClass("java.lang.System");
        printClass = Scene.v().getSootClass("java.io.PrintStream");
        stringClass = Scene.v().getSootClass("java.lang.String");
        dumpMethod = callClass.getMethodByName("dumpAllTraces");
        
        LinkedList<Type> paramTypeList = new LinkedList<Type>();
        paramTypeList.add(stringClass.getType());
        printStringMethod = printClass.getMethod("print", paramTypeList);
        
        paramTypeList.clear();
        paramTypeList.add(BooleanType.v());
        printBoolMethod = printClass.getMethod("print", paramTypeList);
        
        paramTypeList.clear();
        printLineMethod = printClass.getMethod("println", paramTypeList);
        
        systemErr = systemClass.getFieldByName("err");
    }
    
    /**
     * A method which instruments the CallTrace class to add global call coverage
     * static fields.
     * @param methodSig The signature of the method we're keeping coverage for.
     * @param count The number of call sites in the method.
     */
    public static SootField[] makeCallFields(String methodSig, int count)
    {
        LinkedList<Stmt> dumpStmts = new LinkedList<Stmt>();
        Body dumpBody = dumpMethod.retrieveActiveBody();
        Unit insertPoint = dumpBody.getUnits().getLast();
       //Add our printWriter local and temporary bool if not already present
       if (printWriter == null)
       {
           printWriter = Jimple.v().newLocal("___cc_print_writer", printClass.getType());
           printBool = Jimple.v().newLocal("___cc_print_bool", BooleanType.v());
           dumpBody.getLocals().add(printWriter);
           dumpBody.getLocals().add(printBool);
           
           //Link local writer to system one
           AssignStmt linkStmt = Jimple.v().newAssignStmt(printWriter, Jimple.v().newStaticFieldRef(systemErr.makeRef()));
           dumpStmts.add(linkStmt);
       }       
       //Generate the global coverage booleans
       SootField[] globalFields = new SootField[count];
       for (int i = 0; i < count; i++)
       { 
           //NOTE: Due to Jasmin restrictions on variable names, we have to use the HashCode
           //of the method signature when naming these, rather than the actual signature.
           
           globalFields[i] = new SootField(methodSig.hashCode() + "_" + i, BooleanType.v(), Modifier.STATIC | Modifier.PUBLIC);
           callClass.addField(globalFields[i]);
       }
       
       //Add initial print of method signature
       VirtualInvokeExpr printInvoke = Jimple.v().newVirtualInvokeExpr(printWriter, printStringMethod.makeRef(), StringConstant.v(methodSig + ": "));
       InvokeStmt startPrint = Jimple.v().newInvokeStmt(printInvoke);
       dumpStmts.add(startPrint);
       
       //Add code to print each boolean
       for (int i = 0; i < count; i ++)
       {
           dumpStmts.add(Jimple.v().newAssignStmt(printBool, Jimple.v().newStaticFieldRef(globalFields[i].makeRef())));
           VirtualInvokeExpr boolPrintInvoke = Jimple.v().newVirtualInvokeExpr(printWriter, printBoolMethod.makeRef(), 
                   printBool);
           VirtualInvokeExpr spacePrintInvoke = Jimple.v().newVirtualInvokeExpr(printWriter, printStringMethod.makeRef(), 
                   StringConstant.v(" "));
           dumpStmts.add(Jimple.v().newInvokeStmt(boolPrintInvoke));
           dumpStmts.add(Jimple.v().newInvokeStmt(spacePrintInvoke));
       }
       VirtualInvokeExpr linePrintInvoke = Jimple.v().newVirtualInvokeExpr(printWriter, printLineMethod.makeRef());
       dumpStmts.add(Jimple.v().newInvokeStmt(linePrintInvoke));
       for (Stmt s : dumpStmts)
       {
           dumpBody.getUnits().insertBefore(s, insertPoint);
       }       
       return globalFields;
    }
}
